<?php

namespace App\Services;

use App\Jobs\AudioBook;
use App\Libraries\CUOss;
use App\Libraries\MicrosoftSpeech;
use App\Models\Book;
use App\Models\BookRole;
use App\Models\BookStoryRole;
use App\Models\Chapter;
use App\Models\ChapterAudio;
use App\Models\ChapterData;
use App\Models\MicrosoftVoice;
use App\Models\MicrosoftVoicesStyle;
use App\Models\Prompt;
use App\Models\TaskLog;
use Exception;
use Illuminate\Support\Facades\Storage;

class AudioBookJobService
{
    public function callback($book_id, $current_type): bool
    {
        $workflow = [
            'removeInvalidContent' => [
                'type_name' => '去废话',
                'chapter' => 'once', //单章执行
            ],
            'convertStory' => [
                'type_name' => '转话本',
                'chapter' => 'once', //单章执行
            ],
            'storyCheck' => [
                'type_name' => '话本审校',
                'chapter' => 'once', //单章执行
            ],
            'extractRole' => [
                'type_name' => '提取角色、人物+专有名词',
                'chapter' => 'multiple', //所有章节合并执行
            ],
            'translatePersonName' => [
                'type_name' => '翻译人物名字和专有名词',
                'chapter' => 'multiple', //所有章节合并执行
            ],
            'replacePersonName' => [
                'type_name' => '替换人物和专有名词英文名',
                'chapter' => 'multiple', //所有章节合并执行
            ],
            'extractRoleProfile' => [
                'type_name' => '提取角色画像',
                'chapter' => 'multiple', //所有章节合并执行
            ],
            'assignVoice' => [
                'type_name' => '分配语音',
                'chapter' => 'multiple', //所有章节合并执行
            ],
            'translate' => [
                'type_name' => '翻译',
                'chapter' => 'once', //单章执行
            ],
            'addVoiceStyle' => [
                'type_name' => '添加语气',
                'chapter' => 'once', //单章执行
            ],
            'createVoice' => [
                'type_name' => '配音',
                'chapter' => 'once', //单章执行
            ],
            'addBgm' => [
                'type_name' => '添加背景音',
                'chapter' => 'once', //单章执行
            ]
        ];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        $keys = array_keys($workflow);
        $current_index = array_search($current_type, $keys);
        $next_key = $keys[$current_index + 1] ?? '';
        if (empty($next_key)) {
            return true;
        }
        $next_type_name = $workflow[$next_key]['type_name'];
        if ($workflow[$next_key]['chapter'] == 'once') {
            $completion_count = TaskLog::where('book_id', $book_id)->where('type', $current_type)->where('status', 2)->count();
            if ($book->chapter_total == $completion_count) {
                $chapters = Chapter::select('id', 'num')->where('book_id', $book_id)->where('status', 1)->get();
                foreach ($chapters as $chapter) {
                    $job_data = [
                        'type' => $next_key,
                        'type_name' => $next_type_name,
                        'book_id' => $book_id,
                        'chapter_id' => $chapter->id,
                        'chapter_num' => $chapter->num
                    ];
                    $result = $this->dispatchJob($job_data);
                    if (empty($result)) {
                        throw new Exception('下发任务失败');
                    }
                }
            }
        } else {
            $completion = TaskLog::where('book_id', $book_id)->where('type', $current_type)->where('status', 2)->count();
            if (!empty($completion)) {
                $job_data = [
                    'type' => $next_key,
                    'type_name' => $next_type_name,
                    'book_id' => $book_id,
                    'chapter_id' => 0,
                    'chapter_num' => 0
                ];
                $result = $this->dispatchJob($job_data);
                if (empty($result)) {
                    throw new Exception('下发任务失败');
                }
            }
        }

        return true;
    }

    public function dispatchJob($params): bool
    {
        // 记录任务日志
        if (empty($params['book_id']) || !isset($params['chapter_id']) || empty($params['type']) || empty($params['type_name'])) {
            return false;
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];
        $chapter_num = $params['chapter_num'];
        $type = $params['type'];
        $type_name = $params['type_name'];

        $jobLog = TaskLog::updateOrCreate(
            [
                'book_id' => $book_id,
                'chapter_id' => $chapter_id,
                'type' => $type
            ],
            [
                'chapter_num' => $chapter_num,
                'type_name' => $type_name,
                'status' => 0,
                'start_time' => null,
                'end_time' => null,
                'params' => null,
                'err_message' => null
            ]
        );

        $params['log_id'] = $jobLog->id;

        AudioBook::dispatch($params);

        return true;
    }

    /**
     * 去废话
     * @throws Exception
     */
    public function removeInvalidContent($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        $chapter = Chapter::where('book_id', $book_id)->where('id', $chapter_id)->first();
        if (empty($chapter)) {
            throw new Exception('章节不存在');
        }

        // 提示词
        $prompt = Prompt::where('type', 'removeInvalidContent')->value('content');
        $ai_message = $prompt . "\n" . $chapter->original_content;

        $aiChatService = new AiChatService('gemini');
        $result = $aiChatService->sendMessage($ai_message);
        if (!empty($result) && $result['err_code'] === 0) {
            $content = $result['content'];
            $invalid_content = '';
            $pattern = '/【(.*?)】|\((.*?)\)/';
            if (preg_match_all($pattern, $content, $matches)) {
                foreach ($matches[0] as $str) {
                    $invalid_content .= $str . "\n";
                    $content = trim(str_replace($str, '', $content));
                }
            }
            $chapter->content = $content;
            $chapter->invalid_content = $invalid_content;
            $chapter->save();
        }

        return true;
    }

    /**
     * 转话本+标注人物名+标注专有名词
     * @throws Exception
     */
    public function convertStory($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        $chapter = Chapter::where('book_id', $book_id)->where('id', $chapter_id)->first();
        if (empty($chapter) || empty($chapter->content)) {
            throw new Exception('章节内容不存在');
        }
        $input_strlen = mb_strlen($chapter->content);

        $chapterData = ChapterData::firstOrCreate(
            ['book_id' => $book_id, 'chapter_id' => $chapter_id],
            ['chapter_num' => $chapter->num]
        );
        // 提示词
        $prompt = Prompt::where('type', 'convertStory')->value('content');
        $ai_message = $prompt . "\n" . $chapter->content . "\n感谢聆听，我们下期再见。";

        // 获取两遍结果
        $fields = ['story_ai1', 'story_ai2'];
        foreach ($fields as $field) {
            $aiChatService = new AiChatService('gemini');
            $count_tokens = $aiChatService->countTokens($ai_message);
            if ($count_tokens > 8000) {
                throw new Exception('token超出限制');
            }
            $result = $aiChatService->sendMessage($ai_message);
            if (!empty($result) && $result['err_code'] == 0) {
                $content = $result['content'];
                $total_token = $result['total_token'];
                if (stripos($content, '感谢聆听，我们下期再见') === false) {
                    throw new Exception('结尾标识词丢失');
                }
                // 校验字符数
                $output_strlen = mb_strlen($content);
                if (empty($output_strlen)) {
                    throw new Exception($field . 'AI返回结果为空');
                }
                $ratio = (($output_strlen - $input_strlen) / $output_strlen) * 100;
                if ($ratio > 20) {
                    throw new Exception('字符数误差超过20%，输入：' . $input_strlen . '，输出：' . $output_strlen);
                }
                // 校验每一行是否存在对话角色
                $texts = explode("\n", $content);
                foreach ($texts as $index => &$text) {
                    $text = trim($text);
                    if (empty($text)) {
                        unset($texts[$index]);
                        continue;
                    }
                    $pattern = '/【(.*?)】/';
                    if (preg_match($pattern, $text, $matches)) {
                        // 删除角色名中出现的已知字符
                        $role = $matches[0];
                        if (stripos($role, '<') !== false || stripos($role, '>') !== false) {
                            $role = str_replace(['<', '>'], '', $matches[0]);
                            $text = str_replace($matches[0], $role, $text);
                        }
                    } else {
                        throw new Exception('未找到角色名 text：' . $text);
                    }
                }
                $content = implode("\n", $texts);
                if ($field == 'story_ai1') {
                    $chapterData->story_ai1 = $content;
                    $chapterData->story_ai1_token = $total_token;
                } else {
                    $chapterData->story_ai2 = $content;
                    $chapterData->story_ai2_token = $total_token;
                }
                $chapterData->save();
            } else {
                throw new Exception($field . 'AI响应异常');
            }
        }

        return true;
    }

    /**
     * 话本审校
     * @throws Exception
     */
    public function storyCheck($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        $chapterData = ChapterData::where('book_id', $book_id)->where('chapter_id', $chapter_id)->first();
        if (empty($chapterData) || empty($chapterData->story_ai1) || empty($chapterData->story_ai2)) {
            throw new Exception('缺少话本内容');
        }

        // 提示词
        $prompt = Prompt::where('type', 'storyChecking')->value('content');
        $ai_message = $prompt . "\nAI1：\n" . $chapterData->story_ai1 . "\nAI2：\n" . $chapterData->story_ai2;

        $aiChatService = new AiChatService('gemini');
        // 预测token量
        $count_tokens = $aiChatService->countTokens($ai_message);
        if ($count_tokens > 8000) {
            throw new Exception('token超出限制');
        }
        $result = $aiChatService->sendMessage($ai_message);
        if (!empty($result) && $result['err_code'] == 0) {
            $content = $result['content'];
            $total_token = $result['total_token'];
            if (stripos($content, '感谢聆听，我们下期再见') === false) {
                throw new Exception('结尾标识词丢失');
            }
            // 校验每一行是否存在对话角色
            $texts = explode("\n", $content);
            foreach ($texts as $index => &$text) {
                $text = trim($text);
                if (empty($text)) {
                    unset($texts[$index]);
                    continue;
                }
                $pattern = '/【(.*?)】/';
                if (preg_match($pattern, $text, $matches)) {
                    // 删除角色名中出现的已知字符
                    $role = $matches[0];
                    if (stripos($role, '<') !== false || stripos($role, '>') !== false) {
                        $role = str_replace(['<', '>'], '', $matches[0]);
                        $text = str_replace($matches[0], $role, $text);
                    }
                } else {
                    throw new Exception('未找到角色名 text：' . $text);
                }
            }
            $end_text = array_pop($texts);
            if (stripos($end_text, '感谢聆听，我们下期再见') === false) {
                throw new Exception('结尾标识词未出现在最后一行');
            }
            $content = implode("\n", $texts);
            $chapterData->story_check = $content;
            $chapterData->story_check_token = $total_token;
            $chapterData->extract_role_status = 0; //重置提取角色状态
            $chapterData->save();
        } else {
            throw new Exception('AI响应异常');
        }

        return true;
    }

    /**
     * 提取对话角色和标注人物+专有名词
     * @throws Exception
     */
    public function extractRole($params): bool
    {
        if (empty($params['book_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        BookStoryRole::where('book_id', $book_id)->delete();
        BookRole::where('book_id', $book_id)->delete();
        ChapterData::select('id', 'chapter_id', 'chapter_num', 'story_check')->where('book_id', $book_id)->chunk(100, function ($chapterDatas) use ($book_id) {
            foreach ($chapterDatas as $chapterData) {
                if (empty($chapterData->story_check)) {
                    throw new Exception('缺少话本内容');
                }

                $story_content = $chapterData->story_check;
                // 获取对话角色
                $pattern = '/【(.*?)】/';
                if (preg_match_all($pattern, $story_content, $matches)) {
                    $roles = array_unique($matches[1]);
                    $countValues = array_count_values($matches[1]);
                    foreach ($roles as $role) {
                        $num = $countValues[$role]; // 对话次数
                        // 删除名字以外的特殊字符
                        $role = str_replace(['<', '>'], '', $role);
                        $storyRole = BookStoryRole::firstOrCreate(
                            ['book_id' => $book_id, 'name' => $role],
                            ['first_chapter_id' => $chapterData->chapter_id, 'first_chapter_num' => $chapterData->chapter_num]
                        );
                        $storyRole->increment('num', $num);
                    }
                }

                // 获取标注人物+专有名词
                $pattern = '/<([^>]*)>/';
                if (preg_match_all($pattern, $story_content, $matches)) {
                    $roles = array_unique($matches[1]);
                    foreach ($roles as $role) {
                        BookRole::firstOrCreate(['book_id' => $book_id, 'name' => $role]);
                    }
                }

                $chapterData->extract_role_status = 1;
                $chapterData->save();
            }
        });

        return true;
    }

    /**
     * 翻译人物名字和专有名词
     * @throws Exception
     */
    public function translatePersonName($params): bool
    {
        if (empty($params['book_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];

        $chapter_num = Chapter::where('book_id', $book_id)->where('status', 1)->count();
        $chapter_data_num = ChapterData::where('book_id', $book_id)->where('extract_role_status', 1)->count();
        if ($chapter_num != $chapter_data_num) {
            throw new Exception('章节数量不一致');
        }

        $names = BookRole::where('book_id', $book_id)->pluck('name')->toArray();
        $name_count = count($names);
        if (empty($names)) {
            throw new Exception('缺少人物信息');
        }

        $prompt = Prompt::where('type', 'translateRoleName')->value('content');
        $content = implode("\n", $names);
        $ai_message = $prompt . "\n" . $content;
        $aiChatService = new AiChatService('gemini');
        $result = $aiChatService->sendMessage($ai_message);
        if (!empty($result) && $result['err_code'] === 0) {
            $role_list = parse_table($result['content']);
            if ($name_count != count($role_list)) {
                throw new Exception('翻译后角色数量不一致');
            }
        } else {
            throw new Exception('AI翻译结果异常');
        }

        foreach ($role_list as $role) {
            $role = array_values($role);
            list($name, $english_name) = $role;
            BookRole::where('book_id', $book_id)->where('name', $name)->update(['english_name' => $english_name]);
        }

        return true;
    }

    /**
     * 替换话本内人物英文名
     * @throws Exception
     */
    public function replacePersonName($params): bool
    {
        if (empty($params['book_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];

        $chapter_num = Chapter::where('book_id', $book_id)->where('status', 1)->count();
        $chapter_data_num = ChapterData::where('book_id', $book_id)->where('extract_role_status', 1)->count();
        if ($chapter_num != $chapter_data_num) {
            throw new Exception('章节数量不一致');
        }

        // 人物 + 专有名词
        $roles = BookRole::select('name', 'english_name')->where('book_id', $book_id)->get();
        if (empty($roles)) {
            throw new Exception('缺少人物信息');
        }
        $notRoleCount = BookRole::where('book_id', $book_id)->where('english_name', '')->count();
        if ($notRoleCount > 0) {
            throw new Exception('有人物未翻译');
        }

        ChapterData::select('id', 'story_check')->where('book_id', $book_id)->chunk(100, function ($chapterDatas) use ($roles) {
            foreach ($chapterDatas as $chapterData) {
                $story_final = $chapterData->story_check;
                // 替换人物信息
                foreach ($roles as $role) {
                    $story_final = str_replace('<' . $role->name . '>',  ' ' .$role->english_name . ' ', $story_final);
                }
                $chapterData->story = $story_final;
                $chapterData->save();
            }
        });

        return true;
    }

    /**
     * 分析并提取人物信息
     * @throws Exception
     */
    public function extractRoleProfile($params): bool
    {
        if (empty($params['book_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];

        // 所有章节都提取完成后才执行
        $chapter_num = Chapter::where('book_id', $book_id)->where('status', 1)->count();
        $chapter_data_num = ChapterData::where('book_id', $book_id)->where('extract_role_status', 1)->count();
        if ($chapter_num != $chapter_data_num) {
            throw new Exception('章节数量不一致');
        }

        $prompt = Prompt::where('type', 'extractRoleProfile')->value('content');

        $aiChatService = new AiChatService('gemini');
        // 根据人物首次出现的章节，并拼接前后各一章数据提交给AI分析
        $first_chapter_nums = BookStoryRole::where('book_id', $book_id)->groupBy('first_chapter_num')->pluck('first_chapter_num');
        foreach ($first_chapter_nums as $first_chapter_num) {
            if ($first_chapter_num == 1) {
                $chapter_nums = [
                    $first_chapter_num,
                    $first_chapter_num + 1,
                    $first_chapter_num + 2
                ];
            } else {
                $chapter_nums = [
                    $first_chapter_num - 1,
                    $first_chapter_num,
                    $first_chapter_num + 1
                ];
            }
            $role_names = BookStoryRole::where('book_id', $book_id)->where('first_chapter_num', $first_chapter_num)->where('name', '<>', '旁白')->pluck('name')->toArray();
            $story_contents = ChapterData::where('book_id', $book_id)->whereIn('chapter_num', $chapter_nums)->orderBy('chapter_num')->pluck('story_check')->toArray();
            $ai_message = implode("\n", $role_names) . "\n" . $prompt . "\n" . implode("\n", $story_contents);
            $input_tokens = $aiChatService->countTokens($ai_message);
            if ($input_tokens > 8000) {
                throw new Exception('Token数量超出');
            }
            $result = $aiChatService->sendMessage($ai_message);
            if (!empty($result) && $result['err_code'] == 0) {
                $role_profiles = parse_table($result['content']);
                foreach ($role_profiles as $profile) {
                    if (isset($profile['性别'])) {
                        if ($profile['性别'] == '男') {
                            $gender = 'male';
                        } elseif ($profile['性别'] == '女') {
                            $gender = 'female';
                        } else {
                            $gender = 'neutral';
                        }
                    } else {
                        throw new Exception('未提取到性别信息');
                    }
                    $role_name = $profile['角色合并后名称'];
                    if (in_array($role_name, $role_names)) {
                        $story_role_data = [
                            'title' => $profile['包含称谓'],
                            'gender' => $gender,
                            'age' => $profile['推测年龄'],
                            'desc' => $profile['身份描述']
                        ];
                        BookStoryRole::where('book_id', $book_id)->where('name', $role_name)->update($story_role_data);
                    }
                }
            } else {
                throw new Exception('AI翻译结果异常');
            }
        }

        return true;
    }

    /**
     * 分配音色
     * @throws Exception
     */
    public function assignVoice($params): bool
    {
        if (empty($params['book_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];

        $voices = [];
        $genders = ['female', 'male'];
        foreach ($genders as $gender) {
            $list = MicrosoftVoice::select('id', 'chinese_name', 'display_name', 'status')
                ->where('gender', $gender)
                ->orderBy('style_count', 'desc')
                ->orderByRaw("FIELD(status, 'GA', 'Preview')")
                ->get()->toArray();
            $voices[$gender] = $list;
        }
        // 备选语音
        $alternative_voices = $voices;

        $roles = BookStoryRole::where('book_id', $book_id)->orderBy('num', 'desc')->get();
        foreach ($roles as $role) {
            $gender = strtolower($role->gender);
            if ($role->name == '旁白') {
                $gender = 'male';
            }
            $voice = array_shift($voices[$gender]);
            if (!empty($voice)) {
                $role->voice_id = $voice['id'];
                $role->voice_name = $voice['chinese_name'];
                $role->save();
            } else {
                $voice = array_pop($alternative_voices[$gender]);
                if (!empty($voice)) {
                    $role->voice_id = $voice['id'];
                    $role->voice_name = $voice['chinese_name'];
                    $role->save();
                } else {
                    throw new Exception('有角色未分配到语音');
                }
            }
        }

        return true;
    }

    /**
     * 翻译
     * @throws Exception
     */
    public function translate($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }

        $chapterData = ChapterData::where('book_id', $book_id)->where('chapter_id', $chapter_id)->first();
        if (empty($chapterData) || empty($chapterData->story)) {
            throw new Exception('缺少内容');
        }

        $prompt = Prompt::where('type', 'translate')->value('content');
        $ai_message = $prompt . "\n" . $chapterData->story;

        $aiChatService = new AiChatService('gemini');
        $result = $aiChatService->sendMessage($ai_message);
        if (!empty($result) && $result['err_code'] === 0) {
            // 数据验证
            $texts = $result['content'];
            $texts = explode("\n", $texts);
            foreach ($texts as $text) {
                $text = trim($text);
                if (empty($text)) {
                    continue;
                }
                $pattern = '/【(.*?)】/';
                if (preg_match($pattern, $text, $matches)) {
                    //检测角色是否被翻译
                    if (ctype_alpha(str_replace(' ', '', $matches[1]))) {
                        throw new Exception('角色名被翻译了 text：' . $text);
                    }
                    // 检测当前行是否有中文内容
                    $str = str_replace($matches[0], '', $text);
                    if (contains_chinese($str)) {
                        throw new Exception('AI翻译结果中出现中文 text：' . $text);
                    }
                } else {
                    throw new Exception('未找到角色名 text：' . $text);
                }
            }

            $chapterData->translate = $result['content'];
            $chapterData->translate_token = $result['total_token'];
            $chapterData->save();
        } else {
            throw new Exception('AI翻译结果异常');
        }

        // 标记待审校
        Book::where('id', $book_id)->update(['status' => 3]);

        return true;
    }

    /**
     * 增加语气
     * @throws Exception
     */
    public function addVoiceStyle($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $chapterData = ChapterData::where('book_id', $book_id)->where('chapter_id', $chapter_id)->first();
        if (empty($chapterData) || empty($chapterData->translate)) {
            throw new Exception('缺少翻译内容');
        }

        // 说话风格
        $voice_styles = MicrosoftVoicesStyle::groupBy('style')->pluck('style')->toArray();

        $prompt = Prompt::where('type', 'addVoiceStyle')->value('content');
        $ai_message = $prompt . "\n" . $chapterData->translate;

        $aiChatService = new AiChatService('gemini');
        $result = $aiChatService->sendMessage($ai_message);
        if (!empty($result) && $result['err_code'] === 0) {
            // 数据验证
            $texts = $result['content'];
            $texts = explode("\n", $texts);
            foreach ($texts as $text) {
                $text = trim($text);
                if (empty($text)) {
                    continue;
                }
                $pattern = '/【(.*?)】/';
                if (preg_match($pattern, $text, $matches)) {
                    // 检测当前行是否有中文内容
                    $str = str_replace($matches[0], '', $text);
                    if (contains_chinese($str)) {
                        throw new Exception('AI翻译结果中出现中文');
                    }
                    // 检测当前行是否包含对话风格
                    if (preg_match_all('/\((.*?)\)/', $str, $matches)) {
                        $array_count_values = array_count_values($matches[1]);
                        foreach ($matches[1] as $style) {
                            if (in_array($style, $voice_styles) && $array_count_values[$style] != 2) {
                                throw new Exception('情感标记没有成对出现 text：' . $text);
                            }
                        }
                    }
                } else {
                    throw new Exception('未找到角色名 text：' . $text);
                }
            }
            $chapterData->translate_voice_style = $result['content'];
            $chapterData->translate_voice_style_token = $result['total_token'];
            $chapterData->save();
        } else {
            throw new Exception('AI结果异常');
        }

        return true;
    }

    /**
     * 生成音频文件
     * @throws Exception
     */
    public function createVoice($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $book = Book::find($book_id);
        if (empty($book)) {
            throw new Exception('书籍不存在');
        }
        if ($book->status != 7) {
            throw new Exception('等待确认配音');
        }

        $chapterData = ChapterData::where('book_id', $book_id)->where('chapter_id', $chapter_id)->first();
        if (empty($chapterData) || empty($chapterData->translate_voice_style)) {
            throw new Exception('缺少翻译内容');
        }
        $chapter_num = $chapterData->chapter_num;

        $voice_roles = BookStoryRole::select('book_story_roles.name', 'microsoft_voices.short_name')
            ->leftJoin('microsoft_voices', 'book_story_roles.voice_id', '=', 'microsoft_voices.id')
            ->where('book_story_roles.book_id', $book_id)
            ->pluck('microsoft_voices.short_name', 'book_story_roles.name')->toArray();

        $microsoftSpeech = new MicrosoftSpeech;
        $speed = $book->speed >= 1 ? 0 : '-' . $book->speed;
        $texts = $chapterData->translate_voice_style;
        $texts = explode("\n", $texts);

        $ssml_data = [];
        foreach ($texts as $text) {
            $text = trim($text);
            if (empty($text)) {
                continue;
            }
            $pattern = '/【(.*?)】/';
            if (preg_match($pattern, $text, $matches)) {
                $role = trim($matches[1]);
                $content = trim(str_replace($matches[0], '', $text));
            } else {
                throw new Exception('未找到角色名 text：' . $text);
            }
            $content = trim(trim($content, '：'), ':');
            if (empty($role) || empty($content)) {
                throw new Exception('无效行 text：' . $text);
            }

            $short_name = $voice_roles[$role] ?? '';
            if (empty($short_name)) {
                throw new Exception('未匹配到音色 text：' . $text);
            }

            $pattern = '/\((.*?)\)/';
            preg_match_all($pattern, $text, $matches);
            $styles = [];
            foreach ($matches[1] as $val) {
                $styles[] = $val;
            }
            $styles = !empty($styles) ? array_unique($styles) : [];

            $voice_label = '<voice name="' . $short_name . '">';
            if (!empty($styles)) {
                foreach ($styles as $style) {
                    $pattern = '/\(' . $style . '\)(.*?)\(' . $style . '\)/';
                    if (preg_match($pattern, $text, $matches)) {
                        $voice_label .= '<s /><mstts:express-as style="' . $style . '"><prosody rate="' . $speed . '">' . $matches[1] . '</prosody></mstts:express-as><s />';
                    } else {
                        $voice_label .= '<prosody rate="' . $speed . '">' . $content . '</prosody>';
                    }
                }
            } else {
                $voice_label .= '<prosody rate="' . $speed . '">' . $content . '</prosody>';
            }
            $voice_label .= '</voice>';
            $ssml_data[] = $voice_label;
        }

        $CUOss = new CUOss('jowo');
        $limit = 45;
        $length = count($ssml_data);
        if ($length > $limit) {
            $page = ceil($length / $limit);
            $audio_list = [];
            for ($i = 0; $i < $page; $i++) {
                $offset = $i * $limit;
                $list = array_slice($ssml_data, $offset, $limit);
                $ssml = '<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">';
                $ssml .= implode('', $list);
                $ssml .= '</speak>';

                $resource = $microsoftSpeech->textToSpeech($ssml);
                $file_name = $chapter_num . '-' . $i . '.wav';
                $file_root = '/Users/guoqikai/Htdocs/audiobook/' . $book_id . '/audio';
                Storage::build(['driver' => 'local', 'root' => $file_root])->put($file_name, $resource);
                $audio_file = $file_root . '/' . $file_name;
                if (check_media_file($audio_file) === false) {
                    throw new Exception('音频文件无效');
                }
                $audio_list[] = $audio_file;
                $output_file = $file_root . '/' . $chapter_num . '.wav';
                $res = merge_audio($audio_list, $output_file);
                if (!empty($res)) {
                    $object = 'audiobook/' . $book_id . '/audio/' . $chapter_num . '.wav';
                    $res = $CUOss->upload($object, $output_file);
                    if (!empty($res)) {
                        ChapterAudio::updateOrCreate(
                            ['book_id' => $book_id, 'chapter_id' => $chapter_id, 'language' => 'en'],
                            ['audio_src' => $object, 'duration' => get_media_duration($output_file)]
                        );
                    } else {
                        throw new Exception('上传音频文件失败');
                    }
                } else {
                    throw new Exception('合并音频失败');
                }
                unset($ssml);
            }
        } else {
            $ssml = '<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">';
            $ssml .= implode('', $ssml_data);
            $ssml .= '</speak>';

            $resource = $microsoftSpeech->textToSpeech($ssml);
            $file_name = $chapter_num . '.wav';
            $file_root = '/Users/guoqikai/Htdocs/audiobook/' . $book_id . '/audio';
            Storage::build(['driver' => 'local', 'root' => $file_root])->put($file_name, $resource);
            $audio_file = $file_root . '/' . $file_name;
            if (check_media_file($audio_file) === false) {
                throw new Exception('音频文件无效');
            }
            $object = 'audiobook/' . $book_id . '/audio/' . $chapter_num . '.wav';
            $res = $CUOss->upload($object, $audio_file);
            if (!empty($res)) {
                ChapterAudio::updateOrCreate(
                    ['book_id' => $book_id, 'chapter_id' => $chapter_id, 'language' => 'en'],
                    ['audio_src' => $object, 'duration' => get_media_duration($audio_file)]
                );
            } else {
                throw new Exception('上传音频文件失败');
            }
            unset($ssml);
        }

        return true;
    }

    /**
     * 添加背景音
     * @throws Exception
     */
    public function addBgm($params): bool
    {
        if (empty($params['book_id']) || empty($params['chapter_id'])) {
            throw new Exception('缺少参数');
        }

        $book_id = $params['book_id'];
        $chapter_id = $params['chapter_id'];

        $chapterAudio = ChapterAudio::where('book_id', $book_id)->where('chapter_id', $chapter_id)->where('language', 'en')->first();
        if (empty($chapterAudio) || empty($chapterAudio->audio_src)) {
            throw new Exception('缺少音频文件');
        }
        $chapter_num = $chapterAudio->chapter_num;

        $main_audio = '';
        $background_audio = '';
        $output_audio = '';
        $result = mixture_audio($main_audio, $background_audio, $output_audio);
        if (!empty($result)) {
            $object = 'audiobook/' . $book_id . '/audio/bgm_' . $chapter_num . '.wav';
            $CUOss = new CUOss('jowo');
            $res = $CUOss->upload($object, $output_audio);
            if (!empty($res)) {
                $chapterAudio->bgm_audio_src = $object;
                $chapterAudio->bgm_audio_duration = get_media_duration($output_audio);
                $chapterAudio->save();
            } else {
                dd('上传文件失败');
            }
        }

        return true;
    }
}
